request-compose
Composable HTTP Client
var compose = require('request-compose')
var Request = compose.Request
var Response = compose.Response
;(async () => {
try {
var {res, body} = await compose(
Request.defaults({headers: {'user-agent': 'request-compose'}}),
Request.url('https://api.github.com/users/simov'),
Request.send(),
Response.buffer(),
Response.string(),
Response.parse(),
)()
console.log(res.statusCode, res.statusMessage)
console.log(res.headers['x-ratelimit-remaining'])
console.log(body)
}
catch (err) {
console.error(err)
}
})()
Goals
- No dependencies
- No abstraction
- No state
Table of Contents
Compose
In computer science, function composition (not to be confused with object composition) is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.
var compose = require('request-compose')
Accepts a list of functions to execute and returns a Promise:
var doit = compose(
(a) => a + 2,
(a) => a * 2,
)
Then we can call it:
var result = await doit(5)
A more practical example however would be to compose our own HTTP client:
var compose = require('request-compose')
var https = require('https')
var request = compose(
(options) => {
options.headers = options.headers || {}
options.headers['user-agent'] = 'request-compose'
return options
},
(options) => new Promise((resolve, reject) => {
https.request(options)
.on('response', resolve)
.on('error', reject)
.end()
}),
async (res) => await new Promise((resolve, reject) => {
var body = ''
res
.on('data', (chunk) => body += chunk)
.on('end', () => resolve({res, body}))
.on('error', reject)
}),
({res, body}) => ({res, body: JSON.parse(body)}),
)
Then we can use it like this:
;(async () => {
try {
var {res, body} = await request({
protocol: 'https:',
hostname: 'api.github.com',
path: '/users/simov',
})
console.log(res.statusCode, res.statusMessage)
console.log(res.headers['x-ratelimit-remaining'])
console.log(body)
}
catch (err) {
console.error(err)
}
})()
Bundled Middlewares
request-compose
comes with a bunch of pre-defined middlewares for transforming the request and the response:
var compose = require('request-compose')
var Request = compose.Request
var Response = compose.Response
We can use these middlewares to compose our own HTTP client:
;(async () => {
try {
var {res, body} = await compose(
Request.defaults({headers: {'user-agent': 'request-compose'}}),
Request.url('https://api.github.com/users/simov'),
Request.send(),
Response.buffer(),
Response.string(),
Response.parse(),
)()
console.log(res.statusCode, res.statusMessage)
console.log(res.headers['x-ratelimit-remaining'])
console.log(body)
}
catch (err) {
console.error(err)
}
})()
Type | Middleware | Input | Arguments | Returns |
---|
Request | defaults | {input} | {input} | {options} |
Request | url, proxy, qs, cookie | see options | {options} | {options} |
Request | form, json, multipart, body | see options | {options} | {options, body} |
Request | auth, oauth | see options | {options, body} | {options, body} |
Request | length | - | {options, body} | {options, body} |
Request | send | - | {options, body} | {options, res} |
Response | buffer | - | {options, res} | {options, res, body} |
Response | gzip | - | {options, res, body, raw} | {options, res, body, raw} |
Response | string | see options | {options, res, body, raw} | {options, res, body, raw} |
Response | parse, status | - | {options, res, body, raw} | {options, res, body, raw} |
Response | redirect | (input, client) | {options, res, body, raw} | new composition |
Opinionated Client
request-compose
comes with opinionated HTTP client that is composed of the above middlewares.
There are 3 types of composition available based on the returned data type:
client
var request = require('request-compose').client
var {res, body} = await request({options})
The client
composition does the following:
- buffers the response body
- decompresses
gzip
and deflate
encoded bodies with valid content-encoding
header - converts the response body to string using
utf8
encoding by default - tries to parse
JSON
and querystring
encoded bodies with valid content-type
header
Returns either String or Object.
buffer
var request = require('request-compose').buffer
var {res, body} = await request({options})
The buffer
composition does the following:
- buffers the response body
- decompresses
gzip
and deflate
encoded bodies with valid content-encoding
header
Returns Buffer.
stream
var request = require('request-compose').stream
var {res} = await request({options})
The stream
composition returns the response Stream.
options
The above compositions accept any of the Node's http.request and https.request options:
var {res, body} = await request({
method: 'GET',
url: 'https://api.github.com/users/simov',
headers: {
'user-agent': 'request-compose'
}
})
Additionally the following options are available:
Option | Type | Description |
---|
url | 'string' url object | URL (encoding - see below) |
proxy | 'string' url object | Proxy URL |
qs | {object} 'string' | URL querystring (encoding - see below) |
form | {object} 'string' | application/x-www-form-urlencoded request body (encoding - see below) |
json | {object} 'string' | JSON encoded request body |
multipart | {object} [array] | multipart request body using request-multipart, see examples |
body | 'string' Buffer Stream | request body |
auth | {user, pass} | Basic authorization |
oauth | {object} | OAuth 1.0a authorization using request-oauth, see examples |
encoding | 'string' | response body encoding (default: 'utf8') |
cookie | {object} | cookie store using request-cookie, see examples |
redirect | {object} | see below |
Querystring set in the url
, and/or in qs
and/or in form
as 'string' is left untouched, meaning that the proper encoding is left to the user.
When qs
and/or form
is {object} the querystring is encoded using the Node's querystring module which mirrors the global encodeURIComponent method. Additionally all reserved characters according to RFC3986 are encoded as well. Full list of all reserved characters that are being encoded can be found here.
redirect
Option | Default | Description |
---|
max | 3 | maximum number of redirects to follow |
all | false | follow non-GET HTTP 3xx responses as redirects |
method | true | follow original HTTP method, otherwise convert all redirects to GET |
auth | true | keep Authorization header when changing hostnames |
referer | false | add Referer header |
extend
Extend or override any of the bundled request and response middlewares:
var request = require('request-compose').extend({
Request: {
oauth: require('request-oauth'),
multipart: require('request-multipart'),
cookie: require('request-cookie').Request
},
Response: {cookie: require('request-cookie').Response},
}).client
Errors
Non 200/300
responses are thrown as Error object with the following properties:
message
- status code + status messageres
- the response objectbody
- the parsed response bodyraw
- the raw response body
Debug Logs
Fancy request-logs:
npm i --save-dev request-logs
Pick any of the following debug options:
DEBUG=req,res,body,json,nocolor node app.js
Examples